/*
 * MIT License
 * 
 * Copyright (c) 2024-2025 milkpotatoes
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef SRC_JS_UTILS_INCLUDE_JS_TIMER_H
#define SRC_JS_UTILS_INCLUDE_JS_TIMER_H

#include <sys/time.h>
#include <unordered_map>
#include <uv.h>
#include <vector>

#include "napi/native_api.h" // IWYU pragma: keep

class JsTimer;

namespace utils
{
class NapiCallbackInfo;
}

enum TimerType
{
    DEFAULT = 0,
    TIMEOUT,
    INTERVAL,
};

class TimerTask
{
public:
    ~TimerTask();
    static TimerTask *Create(napi_env env, JsTimer *parent, TimerType type);
    void Delete();
    inline void operator delete(void *task)
    {
        reinterpret_cast<TimerTask *>(task)->Delete();
    }
    int Init(uint32_t timeout);
    void Execute() const;
    void Unref();

    napi_ref callback_ = nullptr;
    std::vector<napi_ref> args_;
    const TimerType type_{TimerType::DEFAULT};

private:
    TimerTask(napi_env env, JsTimer *parent, TimerType type) : type_(type), parent_(parent), env_(env)
    {
        napi_add_env_cleanup_hook(env, CleanUp, this);
        handle_.data = nullptr;
    };

    static void UvTimerCallback(uv_timer_t *handle);

    bool isCleanUpHook_ = true;
    static void CleanUp(void *data)
    {
        TimerTask *that = reinterpret_cast<TimerTask *>(data);
        that->isCleanUpHook_ = false;
        delete that;
    }

    JsTimer *parent_{nullptr};
    napi_env env_ = nullptr;
    uv_timer_t handle_;
};

typedef void (*TimerStopCallback)(napi_env env, void *data);

class JsTimer
{
public:
    ~JsTimer();
    JsTimer *New(napi_env env)
    {
        return new JsTimer(env);
    }
    void Delete()
    {
        if (handle_.data != nullptr)
        {
            uv_close(reinterpret_cast<uv_handle_t *>(&handle_), nullptr);
        }
        ::operator delete(this);
    }
    void operator delete(void *ptr)
    {
        reinterpret_cast<JsTimer *>(ptr)->Delete();
    }
    int Init();
    int Deinit();
    static JsTimer *BindToGlobal(napi_env env, napi_value global);

    static napi_value SetTimeout(napi_env env, napi_callback_info info);
    static napi_value SetWeakTimeout(napi_env env, napi_callback_info info);
    static napi_value ClearTimeout(napi_env env, napi_callback_info info);

    static napi_value SetInterval(napi_env env, napi_callback_info info);
    static napi_value SetWeakInterval(napi_env env, napi_callback_info info);
    static napi_value ClearInterval(napi_env env, napi_callback_info info);

    inline static int64_t GetTimeStamp()
    {
        struct timeval time;
        gettimeofday(&time, nullptr);
        return time.tv_sec * 1000 + time.tv_usec / 1000; // 1000: us to ms
    }

protected:
    void CancelTimer(TimerTask *task);
    friend class TimerTask;

private:
    JsTimer(napi_env env);

    template <TimerType type> void CancelTimer(uint32_t id);
    uint32_t CreateTimerTask(napi_env env, const utils::NapiCallbackInfo &info, TimerType timerType);
    uint32_t CreateWeakTimerTask(napi_env env, const utils::NapiCallbackInfo &info, TimerType timerType);

    inline bool HasActivateTimer()
    {
        return taskList_.size() > 0;
    }

    void CheckCallback();
    static void UvCheckCallback(uv_check_t *handle)
    {
        reinterpret_cast<JsTimer *>(handle->data)->CheckCallback();
    }
    void CleanUp()
    {
        Deinit();
    }
    static void CleanUpHook(void *data)
    {
        reinterpret_cast<JsTimer *>(data)->CleanUp();
    }

    static void WarkTimerFinalizer(napi_env, void *data, void *hint)
    {
        reinterpret_cast<JsTimer *>(hint)->CancelTimer(reinterpret_cast<TimerTask *>(data));
    }
    napi_env env_ = nullptr;

    uint32_t nextTaskId_ = 1;
    std::unordered_map<uint32_t, TimerTask *> taskList_;

    uv_check_t handle_;
    void *finalizeData_{nullptr};
};

#endif /* SRC_JS_UTILS_INCLUDE_JS_TIMER_H */
